package com.allwees.bs.c.module.user.service.impl;

import com.google.common.collect.Maps;
import com.allwees.bs.c.common.auth.AuthHandler;
import com.allwees.bs.c.common.auth.AuthHandlerFactory;
import com.allwees.bs.c.common.auth.AuthResult;
import com.allwees.bs.c.common.auth.constant.EAuthType;
import com.allwees.bs.c.module.order.dto.CartDTO;
import com.allwees.bs.c.module.order.service.CartService;
import com.allwees.bs.c.module.user.dto.CheckResult;
import com.allwees.bs.c.module.user.dto.UserDto;
import com.allwees.bs.c.module.user.entity.RetrievePasswdEntity;
import com.allwees.bs.c.module.user.entity.UserCashEntity;
import com.allwees.bs.c.module.user.entity.UserEntity;
import com.allwees.bs.c.module.user.entity.UserPointEntity;
import com.allwees.bs.c.module.user.event.InvalidateTokenEvent;
import com.allwees.bs.c.module.user.event.OAuthUserRegisteredEvent;
import com.allwees.bs.c.module.user.event.UserRegisteredEvent;
import com.allwees.bs.c.module.user.event.UserUpdatedEvent;
import com.allwees.bs.c.module.user.mapper.UserMapper;
import com.allwees.bs.c.module.user.repository.RetrievePasswdRepository;
import com.allwees.bs.c.module.user.repository.UserRepository;
import com.allwees.bs.c.module.user.req.ChangeEmailReq;
import com.allwees.bs.c.module.user.req.ChangePasswordReq;
import com.allwees.bs.c.module.user.req.RegisterReq;
import com.allwees.bs.c.module.user.req.ResetPasswordReq;
import com.allwees.bs.c.module.user.req.SendVerifyEmailReq;
import com.allwees.bs.c.module.user.req.ThirdPartLoginReq;
import com.allwees.bs.c.module.user.req.UserProfileReq;
import com.allwees.bs.c.module.user.service.IUserService;
import com.allwees.bs.c.module.user.service.UserCashService;
import com.allwees.bs.c.module.user.service.UserPointService;
import com.allwees.bs.c.module.user.util.PromoCodeUtil;
import com.allwees.bs.c.module.user.util.ShareCodeUtil;
import com.allwees.bs.c.module.user.util.UserUtil;
import com.allwees.bs.c.module.user.vo.UserProfileVO;
import com.allwees.bs.core.model.user.context.CurrentUser;
import com.allwees.bs.core.modelbase.constant.ResultCode;
import com.allwees.bs.core.modelbase.constant.ResultEnum;
import com.allwees.bs.core.modelbase.exception.BusinessException;
import com.allwees.core.cache.RedisUtil;
import com.allwees.core.common.util.RandomUtil;
import com.allwees.core.common.util.encrypt.MD5Util;
import com.allwees.core.common.util.freemarker.FreeMarkerUtil;
import com.allwees.core.common.util.http.RequestContext;
import com.allwees.sender.EMailSender;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Service("userService")
@Slf4j
public class UserServiceImpl implements UserDetailsService, IUserService {
    @Autowired
    private ApplicationContext context;

    @Autowired
    protected CartService cartService;

    @Autowired
    protected UserRepository userDao;

    @Autowired
    protected RetrievePasswdRepository retrievePasswdDao;

    @Autowired
    protected UserMapper userMapper;

    @Autowired
    protected PasswordEncoder passwordEncoder;

    @Autowired
    protected FreeMarkerUtil freeMarkerUtil;

    @Autowired
    protected EMailSender mailSender;

    @Autowired
    protected UserPointService userPointService;

    @Autowired
    protected UserCashService userCashService;

    @Value("${allwees.apiBasePath}")
    protected String apiBasePath;

    @Value("${allwees.pcBasePath}")
    protected String pcBasePath;

    @Value("${allwees.resetPasswordApi}")
    protected String resetPasswordApi;

    @Value("${facebook.resource.userInfoUri}")
    protected String fbBaseUrl;

    @Value("${google.resource.userInfoUri}")
    protected String googleBaseUrl;

    @Override
    public UserDetails loadUserByUsername(String email) {
        UserEntity user = userDao.findByEmailOrThirdId(email);
        if (user == null) {
            throw new BusinessException(ResultCode.NOT_FOUND,"Invalid username or password.");
        }
        if (user.isDisabled()) {
            throw new BusinessException(ResultCode.DISABLED,"The account is disabled.");
        }
        String username = user.getEmail();
        if (StringUtils.isBlank(username)) {
            username = user.getAppleId();
        }
        if (StringUtils.isBlank(username)) {
            username = user.getFacebookId();
        }
        if (StringUtils.isBlank(username)) {
            username = user.getGoogleId();
        }

        return new CurrentUser(user.getUuid(), username, user.getPassword(),Collections.emptyList());
    }

    @Override
    @Transactional(rollbackOn = Exception.class)
    public UserDto register(RegisterReq req) {
        String email = req.getEmail().toLowerCase();
        req.setEmail(email);
        UserEntity user = userDao.findByEmail(email);
        if (Objects.nonNull(user)) {
            throw BusinessException.getInstance(ResultEnum.EMAIL_ALREADY_EXISTS);
        }

        UserEntity newUser = req.toUserEntity(passwordEncoder);
        updateInviterUuid(newUser);
        newUser = userDao.save(newUser);

        newUser.setShareCode(ShareCodeUtil.idToCode(newUser.getId()));
        newUser.setPromoCode(PromoCodeUtil.idToCode(newUser.getId()));
        userDao.save(newUser);

        UserDto userDto = new UserDto(newUser);
        this.context.publishEvent(new UserRegisteredEvent(userDto));
        return userDto;
    }

    @Override
    @Transactional(rollbackOn = Exception.class)
    public UserDto visitorRegister(RegisterReq req) {
        UserDto userDto = register(req);
        sendVisitorRegisterEmail(req.getEmail(), req.getLastName(), req.getPassword());
        return userDto;
    }

    private void sendVisitorRegisterEmail(String email, String lastName, String password) {
        Map<String, String> map = Maps.newHashMap();
        map.put("email", email);
        map.put("name", lastName);
        map.put("password", password);

        String content = freeMarkerUtil.render("user_visitor_register.ftl", map);
        mailSender.send(email, "Thank you for joining Allwees", content, true);
    }

    private void updateInviterUuid(UserEntity newUser) {
        if (StringUtils.isNotBlank(newUser.getInviterShareCode())) {
            UserEntity inviter = userDao.findByShareCode(newUser.getInviterShareCode());
            if (inviter != null) {
                newUser.setInviterUuid(inviter.getUuid());
            }
        }
    }

    @Override
    @Transactional(rollbackOn = Exception.class)
    public void update(UserEntity user) {
        userDao.save(user);
    }

    @Override
    public UserEntity findByUserUuid(String userUuid) {
        return userDao.getByUuid(userUuid);
    }

    @Override
    public boolean emailExists(String email) {
        return userDao.findByEmail(email) != null;
    }

    @Override
    @Transactional(rollbackOn = Exception.class)
    public void forgetPassword(String email) {
        UserEntity user = userDao.findByEmail(email);
        if (user == null) {
            throw BusinessException.getInstance(ResultEnum.NOT_FOUND);
        }
        long expireNum = System.currentTimeMillis() + 30 * 60 * 1000;

        String resetPasswordSid = getResetPasswordSid(email, expireNum);
        String url = String.format(pcBasePath + resetPasswordApi + "/%s/%s", resetPasswordSid, email);

        RetrievePasswdEntity retrievePasswdEntity = new RetrievePasswdEntity(user.getUuid(), user.getEmail(), resetPasswordSid, expireNum);
        retrievePasswdDao.save(retrievePasswdEntity);
        Map<String, String> map = Maps.newHashMap();
        map.put("email", email);
        map.put("url", url);

        String content = freeMarkerUtil.render("forget.ftl", map);
        mailSender.send(email, "Allwees password reset request", content, true);
    }

    /**
     * 生成忘记密码的sid
     *
     * @return
     */
    private String getResetPasswordSid(String email, long expireNum) {
        // 生成邮件url唯一地址
        String key = RandomUtil.generateString(6);
        return MD5Util.digest(email + "&" + key + "&" + expireNum);
    }

    @Override
    @Transactional(rollbackOn = Exception.class)
    public void resetPassword(ResetPasswordReq rpVo) {
        String email = rpVo.getEmail();
        String sid = rpVo.getSid();
        String password = rpVo.getPassword();
        RetrievePasswdEntity retrievePasswdEntity = retrievePasswdDao.findByUserEmail(email);
        if (Objects.isNull(retrievePasswdEntity)) {
            log.error("重置密码失败,找不到对应的操作,邮件为：" + email);
            throw BusinessException.getInstance(ResultEnum.NOT_FOUND);
        }
        long curTime = System.currentTimeMillis();
        int errorCode = -100;
        String errorMsg = "";
        // 已经被使用
        if (retrievePasswdEntity.isUsed()) {
            errorCode = 101;
            errorMsg = "verify is used";
            // 过期
        } else if (retrievePasswdEntity.getOvertime() < curTime) {
            errorCode = 102;
            errorMsg = "verify time is overdue";
            // 不合法
        } else if (!StringUtils.equals(retrievePasswdEntity.getSid(), sid)) {
            errorCode = 103;
            errorMsg = "verify sid not match";
        }
        retrievePasswdEntity.setUsed(true);
        retrievePasswdDao.save(retrievePasswdEntity);
        if (errorCode > 0) {
            throw BusinessException.getInstance(errorCode, errorMsg);
        }
        UserEntity user = userDao.findByEmail(email);
        user.setPassword(passwordEncoder.encode(password));
        userDao.save(user);
    }


    @Override
    @Transactional(rollbackOn = Exception.class)
    public void changePassword(String userUuid, ChangePasswordReq req) {
        UserEntity user = findByUserUuid(userUuid);
        //验证邮箱是否存在
        if (StringUtils.isBlank(user.getEmail())) {
            throw BusinessException.getInstance(ResultEnum.USER_EMAIL_IS_EMPTY);
        }
        if (req.isVerifyOriginPwd()) {
            //验证密码是否为初始密码
            if (UserUtil.isDefaultPwd(user.getPassword())) {
                throw BusinessException.getInstance(ResultEnum.USERS_NO_PASSWORD_SET);
            }

            //验证用户输入的原始密码是否正确
            if (!UserUtil.checkPassword(req.getOriginPassword(), user.getPassword())) {
                throw BusinessException.getInstance(ResultEnum.USERS_ORIGIN_PASSWORD_NOT_VALID);
            }
        } else {
            req.validateCode(userUuid);
        }
        user.setPassword(passwordEncoder.encode(req.getPassword()));
        userDao.save(user);
        //token置为无效
        this.context.publishEvent(new InvalidateTokenEvent(CurrentUser.getUser()));
    }

    @Override
    @Transactional(rollbackOn = Exception.class)
    public void changeEmail(String userUuid, ChangeEmailReq req) {
        UserEntity user = findByUserUuid(userUuid);
        if (req.isValidatePwd() && UserUtil.isDefaultPwd(user.getPassword())) {
            throw BusinessException.getInstance(ResultEnum.USERS_NO_PASSWORD_SET);
        }
        if (req.isValidatePwd()) {
            req.validatePwd(user.getPassword());
        } else {
            req.validateCode(userUuid);
        }
        user.setEmail(req.getNewEmail());
        userDao.save(user);
        //token置为无效
        this.context.publishEvent(new InvalidateTokenEvent(CurrentUser.getUser()));
    }

    @Override
    public void sendVerifyEmail(String userUuid, SendVerifyEmailReq req) {
        UserEntity user = findByUserUuid(userUuid);

        String code = RandomStringUtils.randomNumeric(6);
        Map<String, String> params = new HashMap<>(2);
        params.put("name", user.getLastName());
        params.put("code", code);

        String content = freeMarkerUtil.render("email_verify_code.ftl", params);
        mailSender.send(req.getEmail(), "Please verify your Email ", content, true);

        RedisUtil.hset(req.getVerifyEmailType().getCacheName(), userUuid, code);
    }

    @Override
    public boolean isDefaultPassword(String userUuid) {
        UserEntity user = findByUserUuid(userUuid);
        return UserUtil.isDefaultPwd(user.getPassword());
    }

    @Override
    @Transactional(rollbackOn = Exception.class)
    public void uploadAvatar(String userUuid, String avatar) {
        UserEntity user = findByUserUuid(userUuid);
        // 记录日志
        user.setAvatar(avatar);
        userDao.save(user);
    }


    /**
     * 更新用户信息
     *
     * @param userProfileReq
     */
    @Override
    public void updateProfile(String userUuid, UserProfileReq userProfileReq) {
        UserEntity user = findByUserUuid(userUuid);
        user.setFirstName(userProfileReq.getFirstName());
        user.setLastName(userProfileReq.getLastName());
        user.setBirthdayYear(userProfileReq.getBirthdayYear());
        user.setBirthdayMonth(userProfileReq.getBirthdayMonth());
        user.setBirthdayDay(userProfileReq.getBirthdayDay());
        user.setGender(userProfileReq.getGender());
        userDao.save(user);
    }

    /**
     * 获取用户信息
     *
     * @param userUuid
     */
    @Override
    public UserProfileVO findProfileByUuid(String userUuid) {
        UserEntity userEntity = userDao.getByUuid(userUuid);
        if (userEntity != null) {
            UserProfileVO userProfileVO = new UserProfileVO();
            userProfileVO.setFirstName(userEntity.getFirstName());
            userProfileVO.setLastName(userEntity.getLastName());
            userProfileVO.setBirthdayYear(userEntity.getBirthdayYear());
            userProfileVO.setBirthdayMonth(userEntity.getBirthdayMonth());
            userProfileVO.setBirthdayDay(userEntity.getBirthdayDay());
            userProfileVO.setGender(userEntity.getGender());
            return userProfileVO;
        }
        return null;
    }

    @Transactional(rollbackOn = Exception.class)
    @Override
    public CheckResult checkUser(ThirdPartLoginReq req) {
        if (req.getType() == null) {
            req.setType(EAuthType.FACEBOOK);
        }
        AuthHandler authHandler = AuthHandlerFactory.getHandler(req.getType());
        AuthResult authResult = authHandler.auth(req);
        UserEntity user = authResult.getUserEntity();

        String agent = RequestContext.getContext().getAgent();
        if (StringUtils.isNotBlank(agent)) {
            user.setAgent(agent.trim());
        }
        if (authResult.isNewUser()) {
            updateInviterUuid(user);
            user = userDao.save(user);
            user.setPromoCode(PromoCodeUtil.idToCode(user.getId()));
            user.setShareCode(ShareCodeUtil.idToCode(user.getId()));
            userDao.save(user);
            context.publishEvent(new OAuthUserRegisteredEvent(new UserDto(user)));
        } else if (authResult.isSave()) {
            user = userDao.save(user);
        }
        if(authResult.isNewUser() || authResult.isSave()){
            context.publishEvent(new UserUpdatedEvent(user));
        }
        return new CheckResult(user,authResult);
    }

    @Override
    public UserDto userInfo(String userUuid) {
        UserDto userDto = userMapper.entity2DTO(findByUserUuid(userUuid));
        CartDTO cart = cartService.getCartByUser(userUuid);
        UserCashEntity cash = userCashService.getCash(userUuid);
        UserPointEntity point = userPointService.getPoint(userUuid);
        userDto.setCartUuid(cart.getUuid());
        userDto.setCartCount(cart.getSum());
        userDto.setCashs(cash != null ? cash.getValue() : BigDecimal.ZERO);
        userDto.setPoints(point != null ? point.getValue() : BigDecimal.ZERO);
        return userDto;
    }

    @Override
    public UserEntity findInviterUser(String userUuid) {
        UserEntity user = userDao.getByUuid(userUuid);
        if (user == null || user.isDisabled()) {
            log.info("user not exsited or disabled userUuid:{}", userUuid);
            return null;
        }
        UserEntity inviter = userDao.getByUuid(user.getInviterUuid());
        if (inviter == null || inviter.isDisabled()) {
            log.info("inviter not exsited or disabled userUuid:{}", userUuid);
            return null;
        }
        return inviter;
    }

    @Override
    public UserEntity findByPromoCode(String orderCouponCode) {
        return userDao.findByPromoCode(orderCouponCode);
    }
}
